package gameengine.map;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import util.Logger;
import xml.Element;
import enumm.GROUND;
import enumm.UNITTYPE;
import gameengine.map.unit.Unit;
import gameengine.map.unit.UnitFactory;

public final class MapFactory extends DefaultHandler {
    enum MAPATTRIBUTES {
        MAP, ROW, FIELD, UNIT
    }

    private static final String TYPE = "type";
    private static final String PARTY = "party";
    private static final String FUEL = "fuel";
    private static final String XSIZE = "xsize";
    private static final String YSIZE = "ysize";

    private final List<Field> area;
    private final Map map;
    private int xSize;
    private int ySize;

    private MapFactory(final InputStream mapFile) {
        area = new LinkedList<Field>();

        try {
            SAXParserFactory.newInstance().newSAXParser().parse(mapFile, this);
        } catch (final SAXException e) {
            Logger.d("Attack.Map()", e.getMessage());
        } catch (final ParserConfigurationException e) {
            Logger.d("Attack.Map()", e.getMessage());
        } catch (final IOException e) {
            Logger.d("Attack.Map()", e.getMessage());
        }

        final Field[] fields = area.toArray(new Field[xSize * ySize]);
        for (int i = 0; i < fields.length; i++) {
            fields[i].setCoordinates((i % xSize) + 1, (i / xSize) + 1);
        }

        map = new Map(xSize, ySize, fields);
    }

    @Override
    public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException {
        final MAPATTRIBUTES t = MAPATTRIBUTES.valueOf(qName.toUpperCase(Locale.getDefault()));

        switch (t) {
        case MAP:
            xSize = Integer.parseInt(attributes.getValue(XSIZE));
            ySize = Integer.parseInt(attributes.getValue(YSIZE));
            break;
        case ROW:
            break;
        case FIELD:
            final GROUND f = GROUND.valueOf(attributes.getValue(TYPE));
            int party = 0;
            if (f == GROUND.BUILDING) {
                party = Integer.parseInt(attributes.getValue(PARTY));
            }
            final Field field = new Field(f, party);
            setField(field);
            break;
        case UNIT:
            final UNITTYPE type = UNITTYPE.valueOf(attributes.getValue(TYPE).toUpperCase());
            final Unit u = UnitFactory.createUnit(type, Integer.parseInt(attributes.getValue(PARTY)));
            area.get(area.size() - 1).setUnit(u);
            break;
        default:
        }
    }

    private boolean setField(final Field field) {
        return area.add(field);
    }

    public static Map readMap(final InputStream mapFile) {
        return new MapFactory(mapFile).map;
    }

    public static void saveMap(final Map map, final FileOutputStream mapSaveFile) {

        final Element rootEle = new Element(MAPATTRIBUTES.MAP.toString());
        rootEle.addAttribute(XSIZE, Integer.toString(map.getXSize()));
        rootEle.addAttribute(YSIZE, Integer.toString(map.getYSize()));

        for (int i = 1; i <= map.getYSize(); i++) {
            final Element row = new Element(MAPATTRIBUTES.ROW.toString());
            for (int j = 1; j <= map.getXSize(); j++) {
                final Element fieldElement = new Element(MAPATTRIBUTES.FIELD.toString());
                final Field f = map.getField(j, i);
                fieldElement.addAttribute(TYPE, f.getGround().toString());
                if (f.getGround() == GROUND.BUILDING) {
                    fieldElement.addAttribute(PARTY, Integer.toString(f.getParty()));
                }

                final Unit u = map.getField(j, i).getUnit();
                if (u != null) {
                    final Element unit = new Element(MAPATTRIBUTES.UNIT.toString());
                    unit.addAttribute(TYPE, u.getUnitType().toString());
                    unit.addAttribute(FUEL, Integer.toString(u.getFuel()));
                    unit.addAttribute(PARTY, Integer.toString(u.getParty()));
                    fieldElement.addChild(unit);
                }
                row.addChild(fieldElement);
            }
            rootEle.addChild(row);
        }

        final PrintWriter out = new PrintWriter(mapSaveFile);
        out.println(rootEle.toString());
        out.close();
    }
}
